package controller

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"
	"v8s-api/rest-api/target/generated-sources"
	"v8s-ssp-proxy/internal/reader"
	"v8s-ssp-proxy/internal/writer"

	"go.uber.org/zap"

	"v8s-ssp-proxy/internal/configuration"
	"v8s-ssp-proxy/internal/types"
	"v8s/logger"
)

const (
	namespacePrefix = "v8s-"

	//errorState = "error"

	resourceStatusHeader          = "ssp-message-type"
	resourceFunctionHeader        = "resource_function:v2"
	resourceStatusHeaderValue     = "resource_status:v2"
	resourceAttributesHeaderValue = "instance_attributes:v2"

	functionCodeConnect = "CONNECT"
	functionCodeDelete  = "REMOVE"
	functionCodePause   = "PAUSE"
	functionCodeResume  = "RESUME"

	stepINIT          = "INIT"
	stepCreateTask    = "CREATE_TENANT"
	stepRemoveService = "REMOVE_SERVICE"
	stepStopping      = "STOPPING"
	stepResume        = "RESUME"

	taskReadyStatus  = "READY"
	taskPausedStatus = "PAUSED"
)

type (
	controller struct {
		log *zap.SugaredLogger

		handlers Handlers
		reader   *reader.Reader
		writer   *writer.Writer

		properties configuration.KafkaProperties
	}

	Handlers interface {
		CreateTask(context.Context, chan types.Step, string, string, string, string, string, string) error
		DeleteTask(context.Context, chan types.Step, string, string) error
		ResumeTask(context.Context, chan types.Step, string, string) error
		StopTask(context.Context, chan types.Step, string, string) error
		GetTask(context.Context, string) (types.Task, error)
	}
)

func NewController(handlers Handlers, reader *reader.Reader, writer *writer.Writer, properties configuration.KafkaProperties) *controller {
	c := &controller{
		log: logger.Logger().Named("controller").Sugar(),

		handlers: handlers,
		reader:   reader,
		writer:   writer,

		properties: properties,
	}

	return c
}

func (c *controller) Start(ctx context.Context) {
	messages := make(chan types.SSPPartnerMessage, c.properties.Concurrency)
	limit := make(chan struct{}, c.properties.Concurrency)

	go func() {
		for {
			select {
			case message := <-messages:
				uuid := message.ResourceId

				c.log.Infof("passing message '%s' to queue", uuid)
				limit <- struct{}{}

				go func() {
					messageCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
					defer func() {
						cancel()
						<-limit

						recovered := recover()
						if recovered != nil {
							c.log.Errorf("failed to handle message '%s': %s", uuid, recovered)
						} else {
							c.log.Infof("message '%s' handled", uuid)
						}
					}()

					c.dispatch(messageCtx, message)
				}()
			case <-ctx.Done():
				err := c.writer.Shutdown()
				if err != nil {
					c.log.Error(err)
				}
				return
			}
		}
	}()

	go c.reader.Read(ctx, messages)
}

func (c *controller) dispatch(ctx context.Context, msg types.SSPPartnerMessage) {
	if msg.Status {
		err := c.handleStatus(ctx, msg)
		if err != nil {
			c.log.With("rqUUID", msg.RqUUID).Errorf("handle status message error: %s", err)
		}
		return
	}

	c.handleFunctionMessage(ctx, msg)
}

func (c *controller) writeStep(step types.Step, from types.SSPPartnerMessage, projectCode string) {

	if !step.Error {
		for _, stageStatus := range step.Status {
			c.doWriteStep(types.Step{Name: step.Name, Content: step.Content}, from, projectCode, stageStatus)
		}
	} else {
		c.doWriteStep(types.Step{Name: step.Name, Content: step.Content}, from, projectCode, "ERROR")
	}
}

func (c *controller) doWriteStep(step types.Step, from types.SSPPartnerMessage, projectCode, stageStatus string) {
	var err error

	msg := types.SSPMessage{
		Common:       from.Common,
		InstanceUUID: from.ResourceId,
		StageStatus:  stageStatus,
	}

	msg.FunctionStatus = functionStatus(step.Name, stageStatus)
	msg.ResourceStatus = resourceStatus(msg.FunctionStatus, step.Name, stageStatus)

	msg.Content, err = initContent(from.ResourceId, step.Content, projectCode)
	if err != nil {
		c.log.Errorf("failed to init content: %s", err)
		return
	}
	location, err := time.LoadLocation("Europe/Moscow")
	if err != nil {
		c.log.Errorf("load time zone error: %s", err)
		return
	}
	msg.Timestamp = time.Now().In(location).UnixNano() / int64(time.Millisecond)
	msg.StageCode = step.Name

	// msg.AWP = awp(msg.ResourceStatus)

	jsonMsg, err := json.Marshal(msg)
	if err != nil {
		c.log.Errorf("failed to marshal msg: %s", err)
		return
	}

	c.log.Infof("writing step: %s", string(jsonMsg))

	header, value := messageHeader(from.FunctionCode)

	c.writer.WriteMsg(from.RqUUID, header, value, jsonMsg)
}

func ParseResourceNameFromV8SAPI(name generated.ResourceName) (types.ResourceName, error) {
	return ParseResourceName(string(name))
}

func ParseResourceName(str string) (types.ResourceName, error) {
	split := strings.Split(str, ":")
	if len(split) < 7 {
		return types.ResourceName{}, fmt.Errorf("expected 7 parts with delimeter ':', but was '%d'", len(split))
	}

	return types.ResourceName{
		Partition:  split[1],
		Service:    split[2],
		Region:     split[3],
		AccountID:  types.ProjectID(split[4]),
		ProjectID:  split[5],
		ResourceID: split[6],
	}, nil
}

func convert(task generated.Task) (types.Task, error) {
	status, err := convertStatus(task.Status)
	if err != nil {
		return types.Task{}, fmt.Errorf("failed to convert status '%s': %w", task.Status, err)
	}

	rn, err := ParseResourceNameFromV8SAPI(task.ResourceName)
	if err != nil {
		return types.Task{}, fmt.Errorf("failed to parse resource name '%s': %w", task.ResourceName, err)
	}

	result := types.Task{
		RN:     rn,
		Status: status,
	}

	if result.Status == readyStatus {
		endpoints := make([]types.Endpoint, 0)
		for cluster, clusterDetails := range task.Clusters.AdditionalProperties {
			for service, serviceInfo := range clusterDetails.Services.AdditionalProperties {
				for _, endPoint := range serviceInfo.Endpoints {
					currentURL := endPoint
					currentType := types.EndpointType(service)
					currentCluster := cluster

					endpoints = append(endpoints, types.Endpoint{
						URL:         currentURL,
						Type:        currentType,
						Description: fmt.Sprintf("service: '%s', cluster: '%s'", currentType, currentCluster),
					})
				}
			}
		}

		result.Endpoints = endpoints
	}

	return result, nil
}

const (
	provisioningStatus types.StatusValue = "PROVISIONING"
	pausedStatus       types.StatusValue = "PAUSED"
	readyStatus        types.StatusValue = "READY"
	errorStatus        types.StatusValue = "ERROR"
	deletedStatus      types.StatusValue = "DELETED"
)

func convertStatus(status generated.TaskStatus) (types.StatusValue, error) {
	var result types.StatusValue

	switch status {
	case generated.TaskStatusResumeRequest:
		result = provisioningStatus
	case generated.TaskStatusCreationRequest:
		result = provisioningStatus
	case generated.TaskStatusSuspensionRequest:
		result = pausedStatus
	case generated.TaskStatusUpdateRequest:
		result = provisioningStatus
	case generated.TaskStatusDeletionRequest:
		result = deletedStatus
	case generated.TaskStatusRunning:
		result = readyStatus
	case generated.TaskStatusSuspended:
		result = pausedStatus
	case generated.TaskStatusError:
		result = errorStatus
	case generated.TaskStatusDeleted:
		result = deletedStatus
	default:
		return types.StatusValue(""), fmt.Errorf("unknown status '%s'", status)
	}

	return result, nil
}
